import Mermaid from "@theme/Mermaid";

# inhibitOnNull

Return a step that yields the same values as the passed in step, but downstream
work is inhibited  when a yielded value is `null` or `undefined`.

:::note[Declarative flow]

The inhibition only affects steps that depend on the wrapper step (and those
that depend on those, and so on); steps depending on the original (unwrapped)
step are not otherwise impacted.

:::

:::info[Under the hood: inhibition of downstream work]

**For those curious only**, you don't need to understand this.

The [execute method of a step class](../step-classes.mdx#execute) is passed
the batched `executionValue` (EV) for each step it depends on to provide the
data needed for execution.

If there is any inhibition, the step will instead be passed a derived set of new
EVs which have the indices (indexes) that are inhibited in any EV removed from
all EVs - i.e. those specific indices are "skipped" and the "batch size" is
adjusted downwards to match. Once the step has executed, the result has these
indices re-populated (with an inhibited value) such that the batch size remains
consistent with other steps in the same _layer plan_.

Should all indices be inhibited, the step will not execute at all.

:::

## Example

```ts
const $parentId = get($post, "parentId");
// If $parentId is null, the `loadOne` will be inhibited (will not execute)
const $parent = loadOne(inhibitOnNull($parentId), batchGetPostById);
```

In the example above, when the `$parentId` step yields `null`, the associated
value for the `loadOne` step will automatically yield `null` (and be inhibited)
without needing to execute. Only the uninhibited non-null values pass through to
the `loadOne` step for execution. If all values for a step are inhibited then the
step will not execute at all. When a value for `$parentId` is not nullish,
execution for that value proceeds as normal.

## Trapping

Combine `inhibitOnNull` with [`trap`](./trap.mdx) if you want to convert an
inhibited value back into a regular `null` (or another value) further down the
plan.

## Plan diagrams

Internally, `inhibitOnNull` currently creates a `__FlagStep`, but that step is
usually converted into a dependency constraint when another step depends on it.
Thus in plan diagrams it's more common to see it on the dependency edge:
instead of a visible node, you will see the dependency arrow annotated with
labels such as `rejectNull`.

<Mermaid chart={`
graph TD
  A["Access"] -->|rejectNull| B["LoadOne"]
`} />

The label signals that downstream work will be inhibited if the value coming
from `Access` is `null`.

## Advanced

`inhibitOnNull` accepts an optional condition step via `{ if: $cond }` (see
[`condition`](./condition) for common conditions). When provided, the value is
only inhibited if both the wrapped step yields `null` and `$cond` yields a
truthy value:

```ts
// Decode `$id` assuming it is the Node `ID` for a user:
const spec = specFromNodeId(userHandler, $id);
// This will represent the value `null` if the ID is null or invalid
const $userIdOrNull = spec.id;

// If `$id` is `null`, we want a literal `null` here.
// However, if `$id` is non-null, but `$userIdOrNull` yields `null`
// (because, for example, an invalid ID or ID for a different type was passed),
// then we want to inhibit all proceeding steps.
const $validUserIdOrNull = inhibitOnNull($userIdOrNull, {
  if: condition("not null", $id),
});
```
